
#include <BScene>
#include <BNode>
#include <BMesh>
#include <BBillboard>

#include <BPickItem>
#include <BPicker>
#include "member_BGroup.h"
#include "member_BNode.h"
#include "member_BShape.h"
#include "member_BPicker.h"
#include "member_BPickItem.h"
#include "member_BBillboard.h"

using namespace BWE;

#define member						(*(member_BPicker*)_ptr)
#define member_allocate()			_ptr = new member_BPicker(this)
#define member_release()			delete (member_BPicker*)_ptr

BPicker::BPicker()
{
	member_allocate();
}
BPicker::BPicker(const BLine& line)
{
	member_allocate();
	this->setLine(line);
}
BPicker::BPicker(const BVector& begin, const BVector& end)
{
	member_allocate();
	this->setLine(begin, end);
}
BPicker::~BPicker()
{
	member_release();
}

void BPicker::setHitNum(int hitNum)
{
	if (hitNum < 0)
		hitNum = 0;
	if (member.hitNum != hitNum)
	{
		member.hitNum = hitNum;
	}
}
int BPicker::hitNum() const
{
	return member.hitNum;
}

void BPicker::setLine(const BVector& begin, const BVector& end)
{
	member.line.set(begin, end);
	member.group = 0;
	member.node = 0;
	member.hitItems.clear();
}
void BPicker::setLine(const BLine& line)
{
	member.line = line;
	member.group = 0;
	member.node = 0;
	member.hitItems.clear();
}
const BLine& BPicker::line() const
{
	return member.line;
}

bool BPicker::exclude(const BObject* object)
{
	if (object && !member.exclusions.contain(object))
	{
		int index = member.exclusions.insert(object);
		return member.exclusions.check(index);
	}
	return false;
}
bool BPicker::include(const BObject* object)
{
	if (!object)
		return false;
	return member.exclusions.remove(object);
}

int BPicker::exclusionCount() const
{
	return member.exclusions.size();
}
const BObject* BPicker::exclusion(int index) const
{
	return member.exclusions(index);
}

void BPicker::clearExclusions()
{
	member.exclusions.clear();
}

BPickItem* BPicker::apply(BScene* scene)
{
	member.group = 0;
	member.node = 0;
	member.shape = 0;
	member.hitItems.clear();
	if (scene)
	{
		this->reset();
		if (scene_member(scene)->sceneOctTree.empty())
		{
			for (int i = 0; i < scene->nodeCount(); i++)
			{
				BNode* node = scene->node(i);
				member.enter(member.line, node);
			}
			for (int i = 0; i < scene->shapeCount(); i++)
			{
				BShape* shape = scene->shape(i);
				member.enter(member.line, shape);
			}
			for (int i = 0; i < scene->groupCount(); i++)
			{
				BGroup* group = scene->group(i);
				member.enter(member.line, group);
			}
		}
		else
		{
			member.sceneOctItems.clear();
			scene_member(scene)->sceneOctTree->intersect(member.line, member.sceneOctItems);
			for (int i = 0; i < member.sceneOctItems.size(); i++)
			{
				SceneSet& sceneSet = *member.sceneOctItems[i];
				for (int i = 0; i < sceneSet.shapes.size(); i++)
				{
					BShape* shape = sceneSet.shapes[i];
					member.enter(member.line, shape);
				}
				for (int i = 0; i < sceneSet.nodes.size(); i++)
				{
					BNode* node = sceneSet.nodes[i];
					member.enter(member.line, node);
				}
			}
			member.sceneOctItems.reserve(member.sceneOctItems.size());
		}
		this->finish();
		return member.hitItems.min();
	}
	return 0;
}
BPickItem* BPicker::apply(BGroup* group)
{
	member.group = 0;
	member.node = 0;
	member.shape = 0;
	member.hitItems.clear();
	if (group)
	{
		this->reset();
		member.enter(member.line, group);
		this->finish();
		return member.hitItems.min();
	}
	return 0;
}
BPickItem* BPicker::apply(BNode* node)
{
	member.group = 0;
	member.node = 0;
	member.shape = 0;
	member.hitItems.clear();
	if (node)
	{
		this->reset();
		member.enter(member.line, node);
		this->finish();
		return member.hitItems.min();
	}
	return 0;
}
BPickItem* BPicker::apply(BShape* shape)
{
	member.group = 0;
	member.node = 0;
	member.shape = 0;
	member.hitItems.clear();
	if (shape)
	{
		this->reset();
		member.enter(member.line, shape);
		this->finish();
		return member.hitItems.min();
	}
	return 0;
}

int BPicker::itemCount() const
{
	return member.hitItems.size();
}
BPickItem* BPicker::item(int index)
{
	return member.hitItems(index);
}
const BPickItem* BPicker::item(int index) const
{
	return member.hitItems(index);
}

void BPicker::clear()
{
	member.group = 0;
	member.node = 0;
	member.shape = 0;
	member.hitItems.clear();
}

bool BPicker::ignore(const BObject* object) const
{
	return member.exclusions.contain(object);
}
bool BPicker::record(BPickItem* item)
{
	if (item)
	{
		if (!item_member(item)->group)
			item_member(item)->group = member.group;
		if (!item_member(item)->node)
			item_member(item)->node = member.node;
		int pos = member.hitItems.size();
		for (int i = 0; i < member.hitItems.size(); i++)
		{
			if (member.hitItems[i]->ratio() > item_member(item)->ratio)
			{
				pos = i;
				break;
			}
		}
		member.hitItems.insert(pos, item);
		if (member.hitItems.size() > member.hitNum)
		{
			member.hitItems.remove(member.hitNum);
		}
		return member.hitItems.contain(item);
	}
	return false;
}
BPickItem* BPicker::create() const
{
	BPickItem* item = new BPickItem();
	item_member(item)->group = member.group;
	item_member(item)->node = member.node;
	item_member(item)->shape = member.shape;
	return item;
}
BMatrix BPicker::matrix() const
{
	BMatrix matrix;
	if (member.shape)
		matrix = member.shape->matrix();
	if (member.node)
		matrix *= member.node->matrix();
	return matrix;
}

void BPicker::reset()
{

}
void BPicker::finish()
{

}

void BPicker::pick(const BLine& line, BGroup* group)
{

}
void BPicker::pick(const BLine& line, BNode* node)
{

}
void BPicker::pick(const BLine& line, BShape* shape)
{

}
void BPicker::pick(const BLine& line, BMesh* mesh)
{
	BVectorArray* vertices = mesh->vertices();
	if (!vertices)
		return;
	bool cullback = mesh->cullback();
	if (mesh_member(mesh)->faceOctTree)
	{
		FaceOctTree* faceOctTree = mesh_member(mesh)->faceOctTree;
		faceOctTree->intersect(line, member.faceOctItems);
		for (int fi = 0; fi < member.faceOctItems.size(); fi++)
		{
			const BFaceArray& faces = *member.faceOctItems[fi];
			for (int i = 0; i < faces.size(); i++)
			{
				const BFace& face = faces[i];
				const BVector& va = (*vertices)[face.a()];
				const BVector& vb = (*vertices)[face.b()];
				const BVector& vc = (*vertices)[face.c()];
				BReal ratio = 0;
				if (line.intersect(va, vb, vc, ratio, cullback))
				{
					BVector normal = (vb - va).cross(vc - va);
					normal.normalize();
					BPickItem* item = new BPickItem(Primitive_Triangles);
					item->setShape(mesh);
					item->setRatio(ratio);
					item->setIndex(i);
					item->setPoint(line.vertex(ratio));
					item->setNormal(normal);
					record(item);
				}
			}
		}
	}
	else
	{
		if (BFaceArray* faces = mesh->faces())
		{
			for (int i = 0; i < faces->size(); i++)
			{
				const BFace& face = (*faces)[i];
				const BVector& va = (*vertices)[face.a()];
				const BVector& vb = (*vertices)[face.b()];
				const BVector& vc = (*vertices)[face.c()];
				BReal ratio = 0;
				if (line.intersect(va, vb, vc, ratio, cullback))
				{
					BVector normal = (vb - va).cross(vc - va);
					normal.normalize();
					BPickItem* item = new BPickItem(Primitive_Triangles);
					item->setRatio(ratio);
					item->setShape(mesh);
					item->setIndex(i);
					item->setPoint(line.vertex(ratio));
					item->setNormal(normal);
					record(item);
				}
			}
		}
		else
		{
			for (int i = 0; i < vertices->size(); i += 3)
			{
				const BVector& va = (*vertices)[i];
				const BVector& vb = (*vertices)[i + 1];
				const BVector& vc = (*vertices)[i + 2];
				BReal ratio = 0;
				if (line.intersect(va, vb, vc, ratio, cullback))
				{
					BVector normal = (vb - va).cross(vc - va);
					normal.normalize();
					BPickItem* item = new BPickItem(Primitive_Triangles);
					item->setRatio(ratio);
					item->setShape(mesh);
					item->setIndex(i / 3);
					item->setPoint(line.vertex(ratio));
					item->setNormal(normal);
					record(item);
				}
			}
		}
	}
}
void BPicker::pick(const BLine& line, BGeometry* geometry)
{
	BVectorArray* vertices = geometry->vertices();
	if (!vertices)
		return;
	bool cullback = geometry->cullback();
	if (geometry->optimized())
	{
		PolyOctTree* polyTree = geometry_member(geometry)->polyTree;
		polyTree->intersect(member.line, member.polyOctItems);
		for (int i = 0; i < member.polyOctItems.size(); i++)
		{
			PolyCell& polyCell = *member.polyOctItems[i];
			for (int i = 0; i < polyCell.triangles.size(); i++)
			{
				BReal ratio = 0;
				BTriangle& triangle = polyCell.triangles[i];
				if (line.intersect(triangle, ratio, cullback))
				{
					BPickItem* item = new BPickItem(Primitive_Triangles);
					item->setShape(geometry);
					item->setRatio(ratio);
					item->setPoint(line.vertex(ratio));
					item->setNormal(triangle.normal());
					item->setElement(polyCell.element);
					item->setIndex(i);
					record(item);
				}
			}
			for (int i = 0; i < polyCell.quads.size(); i++)
			{
				BReal ratio = 0;
				BQuad& quad = polyCell.quads[i];
				if (line.intersect(quad, ratio, cullback))
				{
					BPickItem* item = new BPickItem(Primitive_Quads);
					item->setShape(geometry);
					item->setRatio(ratio);
					item->setPoint(line.vertex(ratio));
					item->setNormal(quad.normal());
					item->setElement(polyCell.element);
					item->setIndex(i);
					record(item);
				}
			}
			for (int i = 0; i < polyCell.polygons.size(); i++)
			{
				BPolygon& polygon = polyCell.polygons[i];

			}
		}
	}
	else
	{
		for (int i = 0; i < geometry->elementCount(); i++)
		{
			BElement* element = geometry->element(i);
			Primitive primitive = element->primitive();
			BIndexArray* indices = element->indices();
			if (indices)
			{
				int size = indices->size();
				if (element->primitive() == Primitive_Triangles)
				{
					for (int i = 0; i < size; i += 3)
					{
						int ia = (*indices)[i];
						int ib = (*indices)[i + 1];
						int ic = (*indices)[i + 2];
						const BVector& va = (*vertices)[ia];
						const BVector& vb = (*vertices)[ib];
						const BVector& vc = (*vertices)[ic];
						BReal ratio;
						if (line.intersect(va, vb, vc, ratio, cullback))
						{
							BVector normal = (vb - va).cross(vc - va);
							normal.normalize();
							BPickItem* item = new BPickItem(Primitive_Triangles);
							item->setShape(geometry);
							item->setRatio(ratio);
							item->setPoint(line.vertex(ratio));
							item->setNormal(normal);
							item->setElement(element);
							item->setIndex(i);
							record(item);
							return;
						}
					}
				}
				if (element->primitive() == Primitive_TriangleStrip)
				{
					for (int i = 0; i < size; i++)
					{
						int ia = (*indices)[i];
						int ib = (*indices)[i + 1];
						int ic = (*indices)[i + 2];
						const BVector& va = (*vertices)[ia];
						const BVector& vb = (*vertices)[ib];
						const BVector& vc = (*vertices)[ic];
						BReal ratio = 0;
						if (line.intersect(va, vb, vc, ratio, cullback))
						{
							BVector normal = (vb - va).cross(vc - va);
							normal.normalize();
							BPickItem* item = new BPickItem(Primitive_Triangles);
							item->setShape(geometry);
							item->setRatio(ratio);
							item->setPoint(line.vertex(ratio));
							item->setNormal(normal);
							item->setElement(element);
							item->setIndex(i);
							this->record(item);
							return;
						}
					}
				}
				if (element->primitive() == Primitive_Quads)
				{
					for (int i = 0; i < size; i += 4)
					{
						int ia = (*indices)[i];
						int ib = (*indices)[i + 1];
						int ic = (*indices)[i + 2];
						int id = (*indices)[i + 3];
						const BVector& va = (*vertices)[ia];
						const BVector& vb = (*vertices)[ib];
						const BVector& vc = (*vertices)[ic];
						const BVector& vd = (*vertices)[id];
						BReal ratio = 0;
						if (line.intersect(va, vb, vc, vd, ratio, cullback))
						{
							BVector normal = (vb - va).cross(vc - va);
							normal.normalize();
							BPickItem* item = new BPickItem(Primitive_Quads);
							item->setShape(geometry);
							item->setRatio(ratio);
							item->setPoint(line.vertex(ratio));
							item->setNormal(normal);
							item->setElement(element);
							item->setIndex(i);
							this->record(item);
							return;
						}
					}
				}
				continue;
			}
			const BPieceArray* pieces = element->pieces();
			if (pieces)
			{
				int size = pieces->size();
				if (element->primitive() == Primitive_Triangles)
				{
					for (int i = 0; i < size; i += 3)
					{
						int ia = (*pieces)[i].v();
						int ib = (*pieces)[i + 1].v();
						int ic = (*pieces)[i + 2].v();
						const BVector& va = (*vertices)[ia];
						const BVector& vb = (*vertices)[ib];
						const BVector& vc = (*vertices)[ic];
						BReal ratio = 0;
						if (line.intersect(va, vb, vc, ratio, cullback))
						{
							BVector normal = (vb - va).cross(vc - va);
							normal.normalize();
							BPickItem* item = new BPickItem(Primitive_Triangles);
							item->setShape(geometry);
							item->setRatio(ratio);
							item->setPoint(line.vertex(ratio));
							item->setNormal(normal);
							item->setElement(element);
							item->setIndex(i);
							this->record(item);
						}
					}
					continue;
				}
				if (element->primitive() == Primitive_Quads)
				{
					for (int i = 0; i < size; i += 4)
					{
						int ia = (*pieces)[i].v();
						int ib = (*pieces)[i + 1].v();
						int ic = (*pieces)[i + 2].v();
						int id = (*pieces)[i + 3].v();
						const BVector& va = (*vertices)[ia];
						const BVector& vb = (*vertices)[ib];
						const BVector& vc = (*vertices)[ic];
						const BVector& vd = (*vertices)[id];
						BReal ratio;
						if (line.intersect(va, vb, vc, vd, ratio, cullback))
						{
							BVector normal = (vb - va).cross(vc - va);
							normal.normalize();
							BPickItem* item = new BPickItem(Primitive_Quads);
							item->setShape(geometry);
							item->setRatio(ratio);
							item->setPoint(line.vertex(ratio));
							item->setNormal(normal);
							item->setElement(element);
							item->setIndex(i);
							this->record(item);
							continue;
						}
					}
					continue;
				}

				continue;
			}
			int first = element->first();
			int count = element->count();
			if (element->primitive() == Primitive_Triangles)
			{
				for (int i = first; i < count; i += 3)
				{
					const BVector& va = (*vertices)[i];
					const BVector& vb = (*vertices)[i + 1];
					const BVector& vc = (*vertices)[i + 2];
					BReal ratio = 0;
					if (line.intersect(va, vb, vc, ratio, cullback))
					{
						BVector normal = (vb - va).cross(vc - va);
						normal.normalize();
						BPickItem* item = new BPickItem(Primitive_Triangles);
						item->setRatio(ratio);
						item->setShape(geometry);
						item->setPoint(line.vertex(ratio));
						item->setNormal(normal);
						item->setElement(element);
						item->setIndex(i);
						this->record(item);
						continue;
					}
				}
				continue;
			}
			if (element->primitive() == Primitive_TriangleStrip)
			{
				for (int i = first; i < count; i++)
				{
					const BVector& va = (*vertices)[i];
					const BVector& vb = (*vertices)[i + 1];
					const BVector& vc = (*vertices)[i + 2];
					BReal ratio = 0;
					if (line.intersect(va, vb, vc, ratio, cullback))
					{
						BVector normal = (vb - va).cross(vc - va);
						normal.normalize();
						BPickItem* item = new BPickItem(Primitive_Triangles);
						item->setRatio(ratio);
						item->setShape(geometry);
						item->setPoint(line.vertex(ratio));
						item->setNormal(normal);
						item->setElement(element);
						item->setIndex(i);
						this->record(item);
						return;
					}
				}
				continue;
			}
			if (element->primitive() == Primitive_Quads)
			{
				for (int i = first; i < count; i += 4)
				{
					const BVector& va = (*vertices)[i];
					const BVector& vb = (*vertices)[i + 1];
					const BVector& vc = (*vertices)[i + 2];
					const BVector& vd = (*vertices)[i + 3];
					BReal ratio = 0;
					if (line.intersect(va, vb, vc, vd, ratio, cullback))
					{
						BVector normal = (vb - va).cross(vc - va);
						normal.normalize();
						BPickItem* item = new BPickItem(Primitive_Quads);
						item->setRatio(ratio);
						item->setShape(geometry);
						item->setPoint(line.vertex(ratio));
						item->setNormal(normal);
						item->setElement(element);
						item->setIndex(i);
						record(item);
						return;
					}
				}
				continue;
			}
		}
	}
}
void BPicker::pick(const BLine& line, BSphere* sphere)
{
	BVector center(0, 0, 0);
	BVector vec = center - line.begin();
	BReal len = vec.dot(line.direct());
	if (len < 0)
		return;
	if (len > line.length())
		return;
	BVector perp = line.perpVertex(center);
	BReal radius = sphere->radius();
	BReal dist0 = (perp - center).length();
	BReal dist = line.distance(center);
	if (dist < radius)
	{
		BReal len = sqrt(radius * radius - dist * dist);
		BVector point = perp - line.direct() * len;
		BReal ratio = (point - line.begin()).length() / line.length();
		BVector normal = point - center;
		normal.normalize();
		BPickItem* item = new BPickItem();
		item->setRatio(ratio);
		item->setShape(sphere);
		item->setPoint(point);
		item->setNormal(normal);
		this->record(item);
		return;
	}
}
void BPicker::pick(const BLine& line, BGeoSphere* geosphere)
{
	BVector perp = line.perpVertex(BVector());
	BReal radius = geosphere->radius();
	BReal dist = perp.length();
	if (dist < radius)
	{
		BReal len = sqrt(radius * radius - dist * dist);
		BVector point = perp - line.direct() * len;
		BReal ratio = (point - line.begin()).length() / line.length();
		BVector normal = point;
		normal.normalize();
		BPickItem* item = new BPickItem();
		item->setRatio(ratio);
		item->setShape(geosphere);
		item->setPoint(point);
		item->setNormal(normal);
		this->record(item);
		return;
	}
}
void BPicker::pick(const BLine& line, BCylinder* cylinder)
{
	pick(line, (BGeometry*)cylinder);
}
void BPicker::pick(const BLine& line, BBillboard* billboard)
{
	const BQuad& quad = billboard_member(billboard)->facingQuad;
	BReal ratio = 0;
	if (line.intersect(quad.a(), quad.b(), quad.c(), quad.d(), ratio))
	{
		BVector normal = quad.normal();
		BPickItem* item = new BPickItem();
		item->setRatio(ratio);
		item->setShape(billboard);
		item->setPoint(line.vertex(ratio));
		item->setNormal(normal);
		record(item);
		return;
	}
}
void BPicker::pick(const BLine& line, BTerrain* terrain)
{
	const BVectorArray* vertices = terrain->vertices();
	const BIndexArray* indices = terrain->indices();
	if (!vertices || !indices)
		return;
	bool cullback = terrain->cullback();
	if (terrain_member(terrain)->quadTree)
	{
		QuadTree* quadTree = terrain_member(terrain)->quadTree;
		quadTree->intersect(line, member.quadItems);
		for (int qi = 0; qi < member.quadItems.size(); qi++)
		{
			BIndexArray& indices = *member.quadItems[qi];
			for (int i = 0; i < indices.size(); i += 4)
			{
				int ia = indices[i];
				int ib = indices[i + 1];
				int ic = indices[i + 2];
				int id = indices[i + 3];
				const BVector& va = (*vertices)[ia];
				const BVector& vb = (*vertices)[ib];
				const BVector& vc = (*vertices)[ic];
				const BVector& vd = (*vertices)[id];
				BReal ratio = 0;
				if (line.intersect(va, vb, vc, vd, ratio, cullback))
				{
					BVector normal = (vb - va).cross(vc - va);
					normal.normalize();
					BPickItem* item = new BPickItem(Primitive_Quads);
					item->setRatio(ratio);
					item->setShape(terrain);
					item->setPoint(line.vertex(ratio));
					item->setNormal(normal);
					item->setIndex(i);
					this->record(item);
				}
			}
		}
	}
	else
	{
		for (int i = 0; i < indices->size(); i += 4)
		{
			int ia = (*indices)[i];
			int ib = (*indices)[i + 1];
			int ic = (*indices)[i + 2];
			int id = (*indices)[i + 3];
			const BVector& va = (*vertices)[ia];
			const BVector& vb = (*vertices)[ib];
			const BVector& vc = (*vertices)[ic];
			const BVector& vd = (*vertices)[id];
			BReal ratio = 0;
			if (line.intersect(va, vb, vc, vd, ratio, cullback))
			{
				BVector normal = (vb - va).cross(vc - va);
				normal.normalize();
				BPickItem* item = new BPickItem(Primitive_Quads);
				item->setRatio(ratio);
				item->setShape(terrain);
				item->setPoint(line.vertex(ratio));
				item->setNormal(normal);
				item->setIndex(i);
				this->record(item);
			}
		}
	}
}
void BPicker::pick(const BLine& line, BSurface* surface)
{
	const BVectorArray* vertices = surface->vertices();
	const BIndexArray* indices = surface->indices();
	if (!vertices || !indices)
		return;
	bool cullback = surface->cullback();
	for (int i = 0; i < indices->size(); i += 4)
	{
		int ia = (*indices)[i];
		int ib = (*indices)[i + 1];
		int ic = (*indices)[i + 2];
		int id = (*indices)[i + 3];
		const BVector& va = (*vertices)[ia];
		const BVector& vb = (*vertices)[ib];
		const BVector& vc = (*vertices)[ic];
		const BVector& vd = (*vertices)[id];
		BReal ratio = 0;
		if (line.intersect(va, vb, vc, vd, ratio, cullback))
		{
			BVector normal = (vb - va).cross(vc - va);
			normal.normalize();
			BPickItem* item = new BPickItem(Primitive_Quads);
			item->setRatio(ratio);
			item->setShape(surface);
			item->setPoint(line.vertex(ratio));
			item->setNormal(normal);
			item->setIndex(i);
			this->record(item);
		}
	}
}

